"""
@Author: Howard Wonanut
@Date  : 2020-11-29
@Github: https://github.com/wonanut
@Descp :
This is the snakeAI project in which I tried to make perfect snakeAI with several methods, like shortest path
strategy, neural network and reinforcement learning. I think it is an interesting project if you really like 
to make some funny games by yourself. 
I tried to use both tkinter and pygame to build the simply but beauty interface, and the performance of pygame
is better, so its the default setting in the ./config.ini file, you can change it to tkinter if you like.
"""

import threading
import time
import pygame
import tkinter as tk

from pygame.locals import *
from view import updateView
from snakeLogit import Snake
from controller import gameController
from utils import configReader
from autoplay import autoPlayGreedy, autoplayBFS, autoplayBFSPlus

def SnakeAI_ThreadFn_Viewer(Snake_object, locker, render, cell_width, sleep_time):
    global canvas
    if render == 'tkinter' and canvas:
        while True:
            locker.acquire()
            updateView(canvas, Snake_object, cell_width, 'tkinter')
            locker.release()

            time.sleep(sleep_time)
    elif render == 'pygame':
        pygame.init()
        pygame.display.set_caption("snakeAI")
        canvas = pygame.display.set_mode([basic_width * col_numbers, basic_width * row_numbers])
        canvas.fill([255, 255, 255])
        clock = pygame.time.Clock()

        quit = True
        while quit:
            clock.tick(5)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    quit = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_SPACE:
                        if snake.getCurrentStatus() == 'paused':
                            snake.setCurrentStatus('alived')
                        elif snake.getCurrentStatus() == 'alived':
                            snake.setCurrentStatus('paused')
                    elif event.key == pygame.K_RETURN:
                        snake.setCurrentStatus('paused')
                        snake.refresh()
                        snake.setCurrentStatus('alived')
            locker.acquire()
            updateView(canvas, Snake_object, cell_width, 'pygame')
            locker.release()

def SnakeAI_ThreadFn_Controller(snake_object, render):
    global canvas
    try:
        if render == 'pygame':
            pass
    
        elif render == 'tkinter':
            windows = tk.Tk()
            windows.title(config_info['windows_title'])

            canvas = tk.Canvas(
                windows, 
                background=config_info['background_color'], 
                width=basic_width * col_numbers, 
                height=basic_width * row_numbers
            )
            canvas.pack()
            canvas.bind_all("<KeyPress>", lambda event: gameController(event, snake_object))

            windows.mainloop()
    except Exception as e:
        print("Exiting...")
        print("Error msg: ", e)

def SnakeAI_ThreadFn_SnakeUpdate(snake_object, locker, sleep_time):
    try:
        while True:
            if snake_object.getCurrentStatus() == 'alived':
                # print("SnakeUpdater acquire lock.")
                locker.acquire()
                snake.forward()
                # print("SnakeUpdater release lock.")
                locker.release()
            time.sleep(sleep_time)
    except Exception as e:
        print("Exiting...")
        print("Error msg: ", e)

if __name__ == "__main__":
    # Read configuration data.
    config_info = configReader(['snakeAI-Basic', 'snakeAI-View'])
    basic_width = int(config_info['basic_block_width'])
    row_numbers = int(config_info['row_block_number'])
    col_numbers = int(config_info['col_block_number'])
    render      = config_info['render']

    # Some sleep time configuration
    auto_play_sleep_time = 0.1
    snake_position_update_sleep_time = 0.2
    view_update_sleep_time = 0.1

    # Build snake object.
    snake = Snake(row_numbers, col_numbers)
    canvas = None

    # lock for update snake.
    snake_update_lock = threading.Lock()
    # Thread for update the frame.
    thread_viewer = threading.Thread(target=SnakeAI_ThreadFn_Viewer, args=(snake, snake_update_lock, render, basic_width, view_update_sleep_time))
    # Thread for listening the keyboard press event.
    thread_controller = threading.Thread(target=SnakeAI_ThreadFn_Controller, args=(snake, render))
    # Thread for update the position of snake.
    thread_updater = threading.Thread(target=SnakeAI_ThreadFn_SnakeUpdate, args=(snake, snake_update_lock, snake_position_update_sleep_time))
    # Thread for autoplay algorithm.
    thread_autoplayer = threading.Thread(target=autoplayBFSPlus, args=(snake, snake_update_lock, auto_play_sleep_time))
    # thread_viewer.setDaemon(True)
    # thread_controller.setDaemon(True)
    # thread_autoplayer.setDaemon(True)
    # thread_updater.setDaemon(True)
    thread_viewer.start()
    thread_controller.start()
    thread_updater.start()
    thread_autoplayer.start()